
#include "tcpclient.h"
#include "messagehandler.h"

#include <iostream>
#include <thread>
#include <boost/property_tree/ptree.hpp>  
#include <boost/property_tree/ini_parser.hpp> 
#include <boost/property_tree/json_parser.hpp> 
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include "common.h"
#include "simpleBMP.h"  // ref：https://github.com/edimetia3d/simpleBMP

using namespace boost;
using boost::asio::ip::tcp;

using ClientConfig_t = std::tuple<bool, int, int>;
using MessageType_t = std::map<std::string, int>;

// load app configure
ClientConfig_t loadConfig(const std::string& filepath)
{
	property_tree::ptree pt;
	property_tree::ini_parser::read_ini(filepath, pt);
	property_tree::ptree c;
    c = pt.get_child("client");
	bool isReconnect = c.get<bool>("IsReconnect");
	int reconnectInterval = c.get<int>("ReconnectInterval");
	int heartbeatInterval = c.get<int>("HeartbeatInterval");
    return std::make_tuple(isReconnect, reconnectInterval, heartbeatInterval);
}

// load message_type file and create message maps
const MessageType_t loadMessageTypes(const std::string& filepath)
{
	using namespace std;
	MessageType_t messageTypes;

	property_tree::ptree pt;
	property_tree::json_parser::read_json(filepath, pt);

	auto parseHex = [](const std::string& str)
	{
		vector <string> fields;
		boost::split(fields, str, boost::is_any_of(","));
		static int out = 0; // static - solve Run-Time Check Failure #2 - Stack around the variable 'out' was corrupted.
		if (kIntLen == fields.size())
		{
            for (auto i = 0; i < kIntLen; i++)
			{
                auto field = boost::trim_copy(fields[i]);
				int x;
				std::stringstream ss;
				ss << std::hex << field;
				ss >> x;
                memset(((char*)&out) + i, x, 1);
			}
		}

		return out;
	};

	try
	{
		for (auto& element : pt)
		{
			if (element.first == "sync_bytes")
			{
				auto typ = parseHex(element.second.data());
				messageTypes.insert(std::pair<std::string, int>(element.first, typ));
			}
			else if (element.first == "message_types")
			{
				for (auto& msgtype : element.second)
				{
					auto typ = parseHex(msgtype.second.data());
					messageTypes.insert(std::pair<std::string, int>(msgtype.first, typ));
				}
			}
			else
			{
				// key - value
				if (element.second.empty())
				{
					std::cout << "key - value:" << element.first << " = " << element.second.data() << std::endl;
				}
				else // just key
				{
					std::cout << element.first << std::endl;
				}
			}
		}
	}
	catch (const std::exception&)
	{
		std::cerr << "loadMessageTypes failed! " << std::endl;
	}

	return messageTypes;
}

int main(int argc, char* argv[])
{
	try
	{
		if (argc != 3)
		{
			std::cerr << "Usage: chat_client <host> <port>\n";
			return 1;
		}
		
		boost::asio::io_service io_service;

		tcp::resolver resolver(io_service);
		tcp::resolver::query query(argv[1], argv[2]);
		tcp::resolver::iterator iterator = resolver.resolve(query);

		// config
        const std::string cfgFilePath = boost::filesystem::current_path().string() + std::string("/app.cfg");
        ClientConfig_t cfg(false, 10, 10);
		if(boost::filesystem::exists(cfgFilePath))
			cfg = loadConfig(cfgFilePath);

		// message type
        const std::string messageTypeFilePath = boost::filesystem::current_path().string() + std::string("/message_types.json");
		MessageType_t messageTypes;
		if (boost::filesystem::exists(messageTypeFilePath))
			messageTypes = loadMessageTypes(messageTypeFilePath);

        if (messageTypes.size() == 0)
        {
            std::cerr << "[E] load message_types.json failed!" << std::endl;
            return -1;
        }

		TcpClient c(io_service, iterator, cfg, messageTypes);

        // add message handlers
        MessageHandler handler(messageTypes, &c);
        c.messagebus().attach<TcpMessage>(kMsgReadHeader, &MessageHandler::onReadHeader, &handler);
        c.messagebus().attach<TcpMessage>(kMsgReadBody, &MessageHandler::onReadBody, &handler);

		// work thread start
		std::thread t([&io_service]() {io_service.run(); });
	
		// read user input message
		const std::string bmpPath = boost::filesystem::current_path().string() + std::string("/lena.bmp");

		auto size1 = sizeof(ClBitMapFileHeader);
		auto size2 = sizeof(ClBitMapInfoHeader);
		auto size3 = sizeof(ClrgbMap) * 256;

		ClImgBMP imgvar;
		imgvar.LoadImage(bmpPath.c_str());
		const int max_body_length = 512;
		char line[max_body_length + 1];
		while (std::cin.getline(line, max_body_length + 1))
		{
			TcpMessage msg;
            msg._msgType = messageTypes["login_req"];
            //std::cout << "[I] [main] msg._msgType: " <<  msg._msgType << std::endl;
            std::stringstream ss;
            ss <<"{ \"msg_type\": \"login_req\", \"data\": {\"body\": \"";
            ss << line;
            ss << "\"} }";
            msg._body = ss.str();
            ss.clear();

			// for test binarydata
			msg._binaryData.resize(imgvar.imgData.size() + size1 + size2 + size3);
			std::copy((char*)&imgvar.bmpFileHeaderData, (char*)&imgvar.bmpFileHeaderData + size1, (char*)msg._binaryData.data());
			std::copy((char*)&imgvar.bmpInfoHeaderData, (char*)&imgvar.bmpInfoHeaderData + size2, (char*)msg._binaryData.data() + size1);
			std::copy((char*)&imgvar.colorMap[0], (char*)&imgvar.colorMap[0] + size3, (char*)msg._binaryData.data() + size1 + size2);
			std::copy((char*)imgvar.imgData.data(), (char*)imgvar.imgData.data() + imgvar.imgData.size(), (char*)msg._binaryData.data() + size1 + size2 + size3);

			c.write(msg);
		}

		c.close();
		t.join();
	}
	catch (std::exception& e)
	{
		std::cerr << "Exception: " << e.what() << "\n";
	}

	return 0;
}
